from enum import Enum
from gettext import gettext as _
import logging
from typing import Any
from collections.abc import Callable
from blueman.bluemantyping import ObjectPath

from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.bluez.Adapter import Adapter

from gi.repository import GLib

from blueman.plugins.applet.StatusIcon import StatusIconProvider


class PowerStateListener:
    def on_power_state_changed(self, manager: "PowerManager", state: bool) -> None:
        return


class PowerStateHandler:
    def on_power_state_query(self) -> "PowerManager.State":
        return PowerManager.State.ON

    def on_power_state_change_requested(self, manager: "PowerManager", state: bool,
                                        cb: Callable[[bool], None]) -> None:
        ...


class PowerManager(AppletPlugin, StatusIconProvider):
    __depends__ = ["Menu"]
    __unloadable__ = True
    __description__ = _("Controls Bluetooth adapter power states")
    __author__ = "Walmis"
    __icon__ = "gnome-power-manager-symbolic"

    class State(Enum):
        ON = 2
        OFF = 1
        OFF_FORCED = 0

    def on_load(self) -> None:
        self.item = self.parent.Plugins.Menu.add(self, 1, text=_("<b>Turn Bluetooth _Off</b>"), markup=True,
                                                 icon_name="bluetooth-disabled-symbolic",
                                                 tooltip=_("Turn off all adapters"),
                                                 callback=self.on_bluetooth_toggled)
        self.adapter_state = True
        self.current_state = True

        self.request_in_progress = False

        self._add_dbus_signal("BluetoothStatusChanged", "b")
        self._add_dbus_method("SetBluetoothStatus", ("b",), "", self.request_power_state)
        self._add_dbus_method("GetBluetoothStatus", (), "b", self.get_bluetooth_status)

    def on_unload(self) -> None:
        self.parent.Plugins.Menu.unregister(self)

    @property
    def CurrentState(self) -> bool:
        return self.current_state

    def on_manager_state_changed(self, state: bool) -> None:
        if state:
            def timeout() -> bool:
                self.request_power_state(self.get_adapter_state())
                return False

            GLib.timeout_add(1000, timeout)

    def get_adapter_state(self) -> bool:
        adapters = self.parent.Manager.get_adapters()
        for adapter in adapters:
            if not adapter["Powered"]:
                return False
        return bool(adapters)

    def set_adapter_state(self, state: bool) -> None:
        try:
            logging.info(state)
            adapters = self.parent.Manager.get_adapters()
            for adapter in adapters:
                adapter.set("Powered", state)

            self.adapter_state = state
        except Exception:
            logging.error("Exception occurred", exc_info=True)

    class Callback:
        def __init__(self, parent: "PowerManager", state: bool):
            self.parent = parent
            self.num_cb = 0
            self.called = 0
            self.state = state
            self.success = False
            self.timer = GLib.timeout_add(5000, self.timeout)

        def __call__(self, result: bool) -> None:
            self.called += 1

            if result:
                self.success = True

            self.check()

        def check(self) -> None:
            if self.called == self.num_cb:
                GLib.source_remove(self.timer)
                logging.info("callbacks done")
                self.parent.set_adapter_state(self.state)
                self.parent.update_power_state()
                self.parent.request_in_progress = False

        def timeout(self) -> bool:
            logging.info("Timeout reached while setting power state")
            self.parent.update_power_state()
            self.parent.request_in_progress = False
            return False

    def request_power_state(self, state: bool, force: bool = False) -> None:
        if self.current_state != state or force:
            if not self.request_in_progress:
                self.request_in_progress = True
                logging.info(f"Requesting {state}")
                cb = PowerManager.Callback(self, state)

                handlers = list(self.parent.Plugins.get_loaded_plugins(PowerStateHandler))
                for handler in handlers:
                    handler.on_power_state_change_requested(self, state, cb)
                cb.num_cb = len(handlers)
                cb.check()
            else:
                logging.info("Another request in progress")

    # queries other plugins to determine the current power state
    def update_power_state(self) -> None:
        rets = [plugin.on_power_state_query()
                for plugin in self.parent.Plugins.get_loaded_plugins(PowerStateHandler)]

        off = any(x != self.State.ON for x in rets) or not self.adapter_state
        foff = self.State.OFF_FORCED in rets
        on = self.State.ON in rets or self.adapter_state

        new_state = True
        if foff or off:

            self.item.set_text(_("<b>Turn Bluetooth _On</b>"), markup=True)
            self.item.set_icon_name("bluetooth-symbolic")
            self.item.set_tooltip(_("Turn on all adapters"))
            self.item.set_sensitive(not foff)

            new_state = False

        elif on and not self.current_state:

            self.item.set_text(_("<b>Turn Bluetooth _Off</b>"), markup=True)
            self.item.set_icon_name("bluetooth-disabled-symbolic")
            self.item.set_tooltip(_("Turn off all adapters"))
            self.item.set_sensitive(True)

            new_state = True

        logging.info(f"off {off} | foff {foff} | on {on} | current state {self.current_state} | new state {new_state}")

        if self.current_state != new_state:
            logging.info(f"Signalling {new_state}")
            self.current_state = new_state

            self._emit_dbus_signal("BluetoothStatusChanged", new_state)
            for plugin in self.parent.Plugins.get_loaded_plugins(PowerStateListener):
                plugin.on_power_state_changed(self, new_state)

            if "StatusIcon" in self.parent.Plugins.get_loaded():
                if new_state:
                    self.parent.Plugins.StatusIcon.set_tooltip_title(_("Bluetooth Enabled"))
                    self.parent.Plugins.StatusIcon.query_visibility(delay_hiding=True)
                else:
                    self.parent.Plugins.StatusIcon.set_tooltip_title(_("Bluetooth Disabled"))
                    self.parent.Plugins.StatusIcon.query_visibility()
                self.parent.Plugins.StatusIcon.icon_should_change()

    def get_bluetooth_status(self) -> bool:
        return self.current_state

    def on_adapter_property_changed(self, _path: ObjectPath, key: str, value: Any) -> None:
        if key == "Powered":
            if value and not self.current_state:
                logging.warning("adapter powered on while in off state, turning bluetooth on")
                self.request_power_state(True)

            self.adapter_state = self.get_adapter_state()
            self.update_power_state()

    def on_bluetooth_toggled(self) -> None:
        self.request_power_state(not self.current_state)

    def on_status_icon_query_icon(self) -> str | None:
        return "blueman-disabled" if not self.get_bluetooth_status() else None

    def on_adapter_added(self, path: ObjectPath) -> None:
        adapter = Adapter(obj_path=path)
        adapter.set("Powered", self.adapter_state)
